package de.hpi.generator.generator.storages;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import de.hpi.generator.helpers.FileHelper;
import java.io.DataOutput;
import java.io.RandomAccessFile;

/**
 * a storage which manages storage
 * @author Michael Wolowyk
 */
public class ValuesDataStorage {

    protected DataOutputStream aDataOutputStream = null;
    protected DataInputStream aDataInputStream = null;
    protected final int aBufferSizeInBytes = 1000000;
    protected String aDataFilePath = null;
    protected int aReadedValuesCount = 0;
    protected int aWritedValuesCount = 0;
     private final int SAVED_ELEMENTS_SIZE_IN_BYTES = 8;

    public String getDataFilePath() {
        return aDataFilePath;
    }

    public ValuesDataStorage(String aDataFilePath) {
        this.aDataFilePath = aDataFilePath;
        updateNumberOfValues();
    }

    /**
     * get number of values in the storage file
     */
    private void updateNumberOfValues() {
        aWritedValuesCount =FileHelper.getFileSize(this.aDataFilePath) / SAVED_ELEMENTS_SIZE_IN_BYTES;
    }

    /**
     * check whether storage is open
     * @return 
     */
    public boolean isOpen() {
        return (isOpenForAppend() || isOpenForRead());
    }
    

    /**
     * check whether storage is open in append mode
     * @return 
     */    
    public boolean isOpenForAppend() {
        return aDataOutputStream != null;
    }

        /**
     * check whether storage is open in read mode
     * @return 
     */    
    public boolean isOpenForRead() {
        return aDataInputStream != null;
    }


    
    /**
    * open storage  in append mode
     * @throws Exception if storage is opened 
     */
    public void openForAppend() throws Exception {
        if (isOpen()) {
            throw new Exception("Cannot open opened storage");
        }
        
        boolean isForAppend = true;
        aDataOutputStream =
                new DataOutputStream(
                new BufferedOutputStream(
                new FileOutputStream(aDataFilePath, isForAppend),
                aBufferSizeInBytes));
    }

        
    /**
    * open storage  in read mode
     * @throws Exception if storage is opened 
     */
    public void openForRead() throws Exception {
        if (isOpen()) {
            throw new Exception("Cannot open opened storage");
        }

        aDataInputStream =
                new DataInputStream(
                new BufferedInputStream(
                new FileInputStream(aDataFilePath),
                aBufferSizeInBytes));

        aReadedValuesCount = 0;
        updateNumberOfValues();
    }

        
    /**
    * close storage after read
     * @throws Exception if storage is closed 
     */
    public void closeAfterRead() throws Exception {
        if (!isOpen()) {
            throw new Exception("Cannot close closed storage");
        }

        aDataInputStream.close();
        aDataInputStream = null;
    }

        /**
    * close storage after append
     * @throws Exception if storage is closed 
     */
    public void closeAfterAppend() throws Exception {
        if (!isOpen()) {
            throw new Exception("Cannot close closed storage");
        }
        aDataOutputStream.close();
        aDataOutputStream = null;
    }

    /**
     * append value seria info to the values.bin storage
     * @param aValueSeria a valueSeria to be appended
     * @throws IOException 
     */
    public void appendValueSeria(ValueSeriaInfo aValueSeria) throws IOException {
        for (int i = 0; i < aValueSeria.getValueCount(); i++) {
            appendValue(aValueSeria);
        }
    }

    /**
     * appends a value to storage
     * First Int: aValue
     * Second Int: aIndex
     * @param aValueSeria
     * @throws IOException 
     */
    public void appendValue(ValueSeriaInfo aValueSeria) throws IOException {
        aDataOutputStream.writeInt(aValueSeria.getValue());
        aDataOutputStream.writeInt(aValueSeria.getUniqueValueIndex());
        aWritedValuesCount++;
    }

    /**
     * reads a valueSeriaInfo from data storage
     * @return valueSeriaInfo with value as first value and valueIndex as second value. 
     * @throws IOException 
     */
    public ValueSeriaInfo readValue() throws IOException {
        ValueSeriaInfo aValueSeriaInfo = new ValueSeriaInfo(
                aDataInputStream.readInt(),
                -1);

        aValueSeriaInfo.setUniqueValueIndex(aDataInputStream.readInt());
        aReadedValuesCount++;
        return aValueSeriaInfo;
    }

    /**
     * gets a number of integers in a file
     * 
     * @return a number of integers in a file
     */
    public int getNumberOfValues() {
        return aWritedValuesCount;
    }
    
  public ValueSeriaInfo updateValueSeriaInfo(ValueSeriaInfo aValueSeriaInfo) throws Exception {
        if (isOpenForAppend()) {
            throw new Exception("Close the storage before update data");
        }

        long aValueSeriaInfoOffsetInBytes = calculateByteOffsetInFileForValueSeriaInfo(aValueSeriaInfo);

        RandomAccessFile aStreamToWrite = new RandomAccessFile(aDataFilePath, "rw");
        aStreamToWrite.seek(aValueSeriaInfoOffsetInBytes);

        writeValueSeriaInfo(aStreamToWrite, aValueSeriaInfo);
        aStreamToWrite.close();

        return aValueSeriaInfo;
    }
  

  
  
   private void writeValueSeriaInfo(DataOutput aOutStream,  ValueSeriaInfo aValueSeriaInfo) throws IOException {
        aOutStream.writeInt(aValueSeriaInfo.getValue());
        aOutStream.writeInt(aValueSeriaInfo.getUniqueValueIndex());
    }
  
     protected long calculateByteOffsetInFileForValueSeriaInfo(ValueSeriaInfo aValueSeriaInfo) {
        return aValueSeriaInfo.getUniqueValueIndex() * SAVED_ELEMENTS_SIZE_IN_BYTES;
    }

     public void clean() throws Exception {
        FileHelper.recreateFile(this.getDataFilePath());
        updateNumberOfValues();
    }


}
